package com.example.demo.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.annotation.ConditionOperator;
import com.example.demo.domain.CombineClause;
import com.example.demo.domain.PageResult;
import com.example.demo.domain.SingleClause;
import com.example.demo.domain.exam.Paging;
import com.example.demo.domain.exam.Sorting;
import com.example.demo.domain.exam.condition.ConditionParser;
import com.example.demo.enums.Operator;
import com.example.demo.exception.ExceptionUtils;
import com.example.demo.service.Clause;

import java.lang.reflect.Field;
import java.util.*;

public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> extends ServiceImpl<M, T> {

    public <C> PageResult<T> selectPage(Clause query, Paging paging, Sorting... sorts) {
        Page<T> page = new Page<>();
        QueryWrapper<T> wrapper = new QueryWrapper<>();

        this.setPage(page, paging);
        this.setSort(wrapper, sorts);
        this.setClause(wrapper, query);
        Page<T> tPage = super.getBaseMapper().selectPage(page, wrapper);
        return PageResult.of(tPage.getRecords()).totalCount(tPage.getTotal());
    }

    public List<T> selectList(Clause query, Sorting... sorts) {
        QueryWrapper<T> wrapper = new QueryWrapper<>();
        this.setClause(wrapper, query);

        this.setSort(wrapper, sorts);
        return super.getBaseMapper().selectList(wrapper);
    }

    public <C> List<T> selectList(C condition, Sorting... sorts) {
        Clause clause = toClause(condition);
        return selectList(clause, sorts);
    }

    public T selectOne(Clause query, Sorting... sorts) {
        QueryWrapper<T> wrapper = new QueryWrapper<>();
        this.setClause(wrapper, query);
        this.setSort(wrapper, sorts);
        return super.getBaseMapper().selectOne(wrapper);
    }
    public int delete(Clause clause) {
        QueryWrapper<T> wrapper = new QueryWrapper<>();
        this.setClause(wrapper, clause);
        return super.getBaseMapper().delete(wrapper);
    }

    public boolean exists(Clause query) {
        QueryWrapper<T> wrapper = new QueryWrapper<>();
        this.setClause(wrapper, query);
        return super.getBaseMapper().selectCount(wrapper) > 0;
    }

    public void modify(HashMap<String, Object> map, Clause clause) {
        UpdateWrapper<T> wrapper = new UpdateWrapper<>();
        map.forEach(wrapper::set);
        setClause(wrapper, clause);
        super.getBaseMapper().update(null, wrapper);
    }

    private void setClause(QueryWrapper<T> wrapper, Clause query) {
        if (query == null) {
            return;
        }
        if (query instanceof CombineClause) {
            CombineClause clauses = (CombineClause) query;
            for (Clause clause : clauses) {
                this.setClause(wrapper, clause);
            }
        } else if (query instanceof SingleClause) {
            SingleClause clause = (SingleClause) query;
            this.setQueryWrapper(wrapper, clause);
        }
    }

    private void setClause(UpdateWrapper<T> wrapper, Clause clauses) {
        if (clauses == null) {
            return;
        }
        if (clauses instanceof CombineClause) {
            CombineClause combineClause = (CombineClause) clauses;
            for (Clause clause : combineClause) {
                this.setClause(wrapper, clause);
            }
        } else if (clauses instanceof SingleClause) {
            SingleClause singleClause = (SingleClause) clauses;
            this.setUpdateWrapper(wrapper, singleClause);
        }
    }

    private void setPage(Page<T> page, Paging paging) {
        if (paging == null) {
            page.setCurrent(1L);
            page.setSize(10L);
        } else {
            Long pageSize = paging.getPageSize();
            Long pageIndex = paging.getPageIndex();
            page.setCurrent(pageIndex);
            page.setSize(pageSize);
        }
    }

    private <C> void setCondition(C condition, QueryWrapper<T> wrapper) {
        if (condition == null) {
            return;
        }
        Class<?> clazz = condition.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            ConditionOperator annotation = field.getAnnotation(ConditionOperator.class);
            if (annotation != null) {
                Operator operator = annotation.operator();
                String name = annotation.name();
                Object value = this.getFieldValue(field, condition);
                if (value == null) {
                    continue;
                }
                SingleClause singleClause = new SingleClause(name, operator, value);
                this.setQueryWrapper(wrapper, singleClause);
            }
        }
    }

    private Object getFieldValue(Field field, Object condition) {
        field.setAccessible(true);
        try {
            return field.get(condition);
        } catch (IllegalAccessException e) {
            throw ExceptionUtils.exception("获取 %s 字段值失败", field.getName());
        }
    }

    private void setUpdateWrapper(UpdateWrapper<T> wrapper, SingleClause singleClause) {
        Operator operator = Optional.ofNullable(singleClause.getOperator()).orElseThrow(() -> ExceptionUtils.exception("比较符不能为空"));
        String name = Optional.ofNullable(singleClause.getName()).orElseThrow(() -> ExceptionUtils.exception("数据库字段不能为空"));
        switch (operator) {
            case Equal:
                wrapper.eq(name, Optional.ofNullable(singleClause.getValue()).orElseThrow(() -> ExceptionUtils.exception("值不能为空")));
                break;
            case NotEqual:
                wrapper.ne(name, Optional.ofNullable(singleClause.getValue()).orElseThrow(() -> ExceptionUtils.exception("值不能为空")));
                break;
            case In:
                wrapper.in(name, Optional.ofNullable(singleClause.getValues()).orElseThrow(() -> ExceptionUtils.exception("值不能为空")));
                break;
            case NotIn:
                wrapper.notIn(name, Optional.ofNullable(singleClause.getValues()).orElseThrow(() -> ExceptionUtils.exception("值不能为空")));
                break;
            case Like:
                wrapper.like(name, Optional.ofNullable(singleClause.getValue()).orElseThrow(() -> ExceptionUtils.exception("值不能为空")));
                break;
            case NotLike:
                wrapper.notLike(name, Optional.ofNullable(singleClause.getValue()).orElseThrow(() -> ExceptionUtils.exception("值不能为空")));
                break;
            case Null:
                wrapper.isNull(name);
                break;
            case NotNull:
                wrapper.isNotNull(name);
                break;
            case LessThan:
                wrapper.lt(name, Optional.ofNullable(singleClause.getValue()).orElseThrow(() -> ExceptionUtils.exception("值不能为空")));
                break;
            case GreaterThan:
                wrapper.gt(name, Optional.ofNullable(singleClause.getValue()).orElseThrow(() -> ExceptionUtils.exception("值不能为空")));
                break;
            case LessThanEqual:
                wrapper.le(name, Optional.ofNullable(singleClause.getValue()).orElseThrow(() -> ExceptionUtils.exception("值不能为空")));
                break;
            case GreaterThanEqual:
                wrapper.ge(name, Optional.ofNullable(singleClause.getValue()).orElseThrow(() -> ExceptionUtils.exception("值不能为空")));
                break;
            case Between:
            case Child:
                // Ignore
                break;
            default:
                throw ExceptionUtils.exception("该比较符号不存在：%s", operator.getAlias());
        }

    }

    private void setQueryWrapper(QueryWrapper<T> wrapper, SingleClause singleClause) {
        Operator operator = Optional.ofNullable(singleClause.getOperator()).orElseThrow(() -> ExceptionUtils.exception("比较符不能为空"));
        String name = Optional.ofNullable(singleClause.getName()).orElseThrow(() -> ExceptionUtils.exception("数据库字段不能为空"));
        switch (operator) {
            case Equal:
                wrapper.eq(name, Optional.ofNullable(singleClause.getValue()).orElseThrow(() -> ExceptionUtils.exception("值不能为空")));
                break;
            case NotEqual:
                wrapper.ne(name, Optional.ofNullable(singleClause.getValue()).orElseThrow(() -> ExceptionUtils.exception("值不能为空")));
                break;
            case In:
                wrapper.in(name, Optional.ofNullable(singleClause.getValues()).orElseThrow(() -> ExceptionUtils.exception("值不能为空")));
                break;
            case NotIn:
                wrapper.notIn(name, Optional.ofNullable(singleClause.getValues()).orElseThrow(() -> ExceptionUtils.exception("值不能为空")));
                break;
            case Like:
                wrapper.like(name, Optional.ofNullable(singleClause.getValue()).orElseThrow(() -> ExceptionUtils.exception("值不能为空")));
                break;
            case NotLike:
                wrapper.notLike(name, Optional.ofNullable(singleClause.getValue()).orElseThrow(() -> ExceptionUtils.exception("值不能为空")));
                break;
            case Null:
                wrapper.isNull(name);
                break;
            case NotNull:
                wrapper.isNotNull(name);
                break;
            case LessThan:
                wrapper.lt(name, Optional.ofNullable(singleClause.getValue()).orElseThrow(() -> ExceptionUtils.exception("值不能为空")));
                break;
            case GreaterThan:
                wrapper.gt(name, Optional.ofNullable(singleClause.getValue()).orElseThrow(() -> ExceptionUtils.exception("值不能为空")));
                break;
            case LessThanEqual:
                wrapper.le(name, Optional.ofNullable(singleClause.getValue()).orElseThrow(() -> ExceptionUtils.exception("值不能为空")));
                break;
            case GreaterThanEqual:
                wrapper.ge(name, Optional.ofNullable(singleClause.getValue()).orElseThrow(() -> ExceptionUtils.exception("值不能为空")));
                break;
            case Between:
            case Child:
                // Ignore
                break;
            default:
                throw ExceptionUtils.exception("该比较符号不存在：%s", operator.getAlias());
        }
    }

    private void setSort(QueryWrapper<T> wrapper, Sorting... sorts) {
        if (sorts == null || sorts.length < 1) {
            return;
        }
        for (Sorting sort : sorts) {
            boolean isAsc = sort.getMode() == Sorting.SortingMode.Ascending;
            List<String> list = Arrays.asList(sort.getFields());
            wrapper.orderBy(true, isAsc, list);
        }
    }

    private <C> Clause toClause(C condition) {
        if (condition instanceof Clause) {
            return (Clause) condition;
        } else {
            return ConditionParser.parser(condition);
        }
    }

}
